#include "StdAfx.h"
#include "WMPMenu.h"
#include "..\WMP\Common.h"
#include "WMPPlusCore.h"
#include "WMPPlusHookData.h"
#include "WMPPlus.h"

#pragma unmanaged

int FindMenuItem(HMENU hMenu, UINT id) {
	if (id == -1)
		return -1;
	int count = GetMenuItemCount(hMenu);
	for(int k = 0; k < count; k++) {
		if (GetMenuItemID(hMenu, k) == id)
			return k;
	}
	return -1;
}

void ClearMenu(HMENU hMenu) {
	while(GetMenuItemCount(hMenu) > 0) {
		DeleteMenu(hMenu, 0u, MF_BYPOSITION);
	}
}

WMPMenuItem::WMPMenuItem(WMPMenu* owner, LPTSTR text) {
	this->owner = owner;
	this->text = text && *text ? _tcsdup(text) : NULL;
	this->checked = false;
	this->enabled = true;
	if (InitID())
		owner->AddItem(this);
}

WMPMenuItem::~WMPMenuItem() {
	if (this->text)
		free((void*)this->text);
	this->owner->RemoveItem(this);
}

WMPMenuItem* WINAPI CreateWMPMenuItem(WMPMenu* menu, LPTSTR text) {
	return new WMPMenuItem(menu, text);
}

void WINAPI DestroyWMPMenuItem(WMPMenuItem *item) {
	if (item != NULL)
		delete item;
}

WMPSubMenu::WMPSubMenu(WMPMenu* owner, LPTSTR text, SUBMENU_PLACE place, UINT id) {
	this->owner = owner;
	this->hMenu = NULL;
	this->text = _tcsdup(text);
	this->owner->AddSubMenu(this);
	this->place = place;
	this->id = id;
}

WMPSubMenu::~WMPSubMenu() {
	this->owner->RemoveSubMenu(this);
}

WMPSubMenu* WINAPI CreateWMPSubMenu(WMPMenu* menu, LPTSTR text, SUBMENU_PLACE place, UINT id) {
	return new WMPSubMenu(menu, text, place, id);
}

void WINAPI DestroyWMPSubMenu(WMPSubMenu *menu) {
	if (menu != NULL)
		delete menu;
}

WMPMenu::WMPMenu() {
	this->id = MENU_MIN_ID;
	this->items = new List<WORD, WMPMenuItem*>();
	this->menus = new List<HMENU, WMPSubMenu*>();
	this->submenus = new CArray<WMPSubMenu*>();
}

WMPMenu::~WMPMenu() {
	Clear();
	delete this->menus;
	delete this->items;
	delete this->submenus;
}

bool WMPMenu::NextID(int pool, WORD &minID, WORD &maxID) {
	if (this->id > MENU_MAX_ID)
		return false;
	minID = this->id;
	pool = min(pool, MENU_MAX_ID - this->id + 1);
	this->id += (WORD)pool;
	maxID = (WORD)(this->id - 1);
	return true;
}

bool WMPMenuItem::Insert(HMENU hMenu, BOOL byPos, UINT pos) {
	if (this->id == 0)
		return false;
	MENUITEMINFO info;
	memset(&info, 0, sizeof(MENUITEMINFO));
	info.cbSize = sizeof(MENUITEMINFO);
	info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_ID | MIIM_STATE;
	info.fType = MFT_STRING;
	info.dwTypeData = this->text;
	info.cch = (UINT)_tcslen(this->text);
	info.wID = this->id;
	info.dwItemData = (ULONG_PTR)this;
	info.fState =
		(this->checked ? MFS_CHECKED : MFS_UNCHECKED) |
		(this->enabled ? MFS_ENABLED : MFS_DISABLED);
	return !!InsertMenuItem(hMenu, pos, byPos, &info);
}

BOOL WINAPI InsertWMPMenuItem(WMPMenuItem* item, HMENU hMenu, int pos) {
	if (!item)
		return false;
	return item->Insert(hMenu, true, pos);
}

void WMPMenu::AddItem(WMPMenuItem* item) {
	WORD id = item->getID();
	this->items->Add(id, item);
}

void WMPMenu::RemoveItem(WMPMenuItem* item) {
	WORD id = item->getID();
	INT_PTR pos = this->items->IndexOf(id);
	if (pos >= 0)
		this->items->Remove(pos);
}

void WMPMenu::AddSubMenu(WMPSubMenu* menu) {
	this->submenus->Add(menu);
}

void WMPMenu::RemoveSubMenu(WMPSubMenu* menu) {
	for(INT_PTR k = this->submenus->GetCount(); k >= 0; k--)
		if (this->submenus->GetAt(k) == menu)
			this->submenus->RemoveAt(k);
}

void WMPMenu::AddMenu(WMPSubMenu* menu) {
	HMENU hMenu = menu->getMenu();
	this->menus->Add(hMenu, menu);
}

void WMPMenu::RemoveMenu(WMPSubMenu* menu) {
	for(INT_PTR k = this->menus->GetCount(); k >= 0; k--)
		if (this->menus->GetData(k) == menu)
			this->menus->Remove(k);
}

void WMPMenu::DeleteSubMenu(HMENU hMenu, UINT item) {
	MENUITEMINFO info;
	memset(&info, 0, sizeof(MENUITEMINFO));
	info.cbSize = sizeof(MENUITEMINFO);
	info.fMask = MIIM_DATA | MIIM_SUBMENU;
	if (!GetMenuItemInfo(hMenu, item, true, &info) ||
		info.dwItemData != (ULONG_PTR)this || info.hSubMenu == NULL)
		return;
	DeleteMenu(hMenu, item, MF_BYPOSITION);
}

void WMPMenu::Clear() {
	HMENU hMenu = Core->Hook->getMainMenu();
	if (!hMenu)
		return;
	int count = GetMenuItemCount(hMenu);
	for (int k = count - 1; k >= 0; k--) {
		HMENU hSubMenu = GetSubMenu(hMenu, k);
		if (hSubMenu == NULL)
			continue;
		int count2 = GetMenuItemCount(hSubMenu);
		for (int j = count2 - 1; j >= 0; j--) {
			DeleteSubMenu(hSubMenu, j);
		}
	}
}


bool WMPMenu::InvokeMenuItem(HWND hWnd, WORD id) {
	if (id < MENU_MIN_ID || id > MENU_MAX_ID)
		return false;
	INT_PTR pos = this->items->IndexOf(id);
	if (pos < 0)
		return false;
	WMPMenuItem* item = this->items->GetData(pos);
	return item->Invoke(hWnd);
}

void WMPMenu::InvokeSubMenu(HWND hWnd, HMENU hMenu) {
	WMPSubMenu* menu;
	MENUITEMINFO info;
	CreateTextBuffer(textBuffer);
	bool iswmplayer = Plugin->getWMPlayerWindow() == hWnd;

	if (iswmplayer) {
		INT_PTR pos = this->menus->IndexOf(hMenu);
		if (pos >= 0) {
			menu = this->menus->GetData(pos);
			menu->Invoke(hMenu);
			return;
		}
	}
	int count = GetMenuItemCount(hMenu);
	int id; WMPMenuItem* item;
	for (int k = count - 1; k >= 0; k--) {
		item = NULL;
		id = GetMenuItemID(hMenu, k);
		if (id >= MENU_MIN_ID && id <= MENU_MAX_ID) {
			memset(&info, 0, sizeof(MENUITEMINFO));
			info.cbSize = sizeof(MENUITEMINFO);
			info.fMask = MIIM_DATA;
			if (GetMenuItemInfo(hMenu, k, true, &info))
				item = (WMPMenuItem*)info.dwItemData;
			WORD uid = (WORD)id;
			INT_PTR pos = this->items->IndexOf(uid);
			if (pos >= 0 && item != this->items->GetData(pos)) {
				_sntprintf(textBuffer, textBufferLength, _T("WMPMenu: menu item ID conflict detected for ID %d"), id);
				OutputDebugString(textBuffer);
				RemoveItem(item);
				if (item->InitID())
					AddItem(item);
			}
		} else if (iswmplayer && id == -1) {
			DeleteSubMenu(hMenu, k);
		}
	}
	if (iswmplayer && Core->Hook->getMainMenu())
		for(INT_PTR pos = 0; pos < this->submenus->GetCount(); pos++) {
			WMPSubMenu* submenu = this->submenus->GetAt(pos);
			int k = submenu->Place(hMenu);
			if (k == -1)
				continue;
			memset(&info, 0, sizeof(MENUITEMINFO));
			info.cbSize = sizeof(MENUITEMINFO);
			info.fMask = MIIM_DATA | MIIM_TYPE | MIIM_SUBMENU;
			info.fType = MFT_STRING;
			info.dwTypeData = submenu->getText();
			info.cch = (UINT)_tcslen(submenu->getText());
			info.hSubMenu = CreateMenu();
			info.dwItemData = (ULONG_PTR)this;
			if (InsertMenuItem(hMenu, k, true, &info))
				submenu->setMenu(info.hSubMenu);
			else
				submenu->setMenu(NULL);
		}
}

int WMPSubMenu::Place(HMENU hMenu) {
	if (!Host)
		return -1;
	int pos = this->id;
	HRESULT hr;
	switch(this->place) {
	case SUBMENU_PLACE_BEGIN_POS:
		if (pos >= 0 && pos <= GetMenuItemCount(hMenu))
			return pos;
		break;
	case SUBMENU_PLACE_END_POS:
		if (pos >= 0)
			pos = GetMenuItemCount(hMenu) - pos;
		if (pos >= 0)
			return pos;
		break;
	case SUBMENU_PLACE_BEFORE_ID:
		pos = FindMenuItem(hMenu, pos);
		if (pos != -1)
			return pos;
		break;
	case SUBMENU_PLACE_AFTER_ID:
		pos = FindMenuItem(hMenu, pos);
		if (pos != -1)
			return pos+1;
		break;
	case SUBMENU_PLACE_CUSTOM:
		hr = Host->PlaceSubMenu(this, hMenu, pos);
		if (SUCCEEDED(hr))
			return pos;
		break;
	}
	return -1;
}

bool WMPMenuItem::InitID() {
	if (this->owner->NextID(1, this->id, this->id))
		return true;
	this->id = 0;
	return false;
}

void WMPSubMenu::setMenu(HMENU hMenu) {
	if (this->hMenu == hMenu)
		return;
	if (this->hMenu)
		owner->RemoveMenu(this);
	this->hMenu = hMenu;
	owner->AddMenu(this);
}

bool WMPMenuItem::Invoke(HWND hWnd) {
	if (!Host)
		return false;
	Host->InvokeMenuItem(this, hWnd);
	return true;
}

void WMPSubMenu::Invoke(HMENU hMenu) {
	ClearMenu(hMenu);
	if (Host)
		Host->InvokeSubMenu(this, hMenu);
}
